<!DOCTYPE html>
<html lang="zh-cn">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>mindMap</title>
<uieip type="file" value="inc" src="/yqwin/easyui/include.txt" cache="true" minute="1440"></uieip>
<uieip type="file" value="css" src="/yqwin/css/yqwin.min" cache="false"></uieip>
<uieip type="file" value="js" src="/yqwin/data/yqwin.min" cache="false"></uieip>
<script src="/scripts/gojs/go.js"></script>
<body>
  <a href="javascript:window.history.back();">返回</a>
  <div id="myDiagramDiv"
    style="border: 1px solid black; width: 100%; height: 300px; position: relative; font: 13px sans-serif;">
  </div>
  <button id="SaveButton" onclick="save()" disabled="">Save</button>
  <button onclick="load()">Load</button>
  <button onclick="layoutAll()">Layout</button>
  <br>
  <textarea id="mySavedModel" style="width: 100%; height: 400px">
  </textarea>
  <script>
    var jsonVal = {
      "class": "go.TreeModel",
      "nodeDataArray": [
        { "key": 0, "text": "Mind Map", "loc": "0 0" },
        { "key": 1, "parent": 0, "text": "Getting more time", "brush": "skyblue", "dir": "right" },
        { "key": 11, "parent": 1, "text": "Wake up early", "brush": "skyblue", "dir": "right" },
        { "key": 12, "parent": 1, "text": "Delegate", "brush": "skyblue", "dir": "right" },
        { "key": 13, "parent": 1, "text": "Simplify", "brush": "skyblue", "dir": "right" },
        { "key": 2, "parent": 0, "text": "More effective use", "brush": "darkseagreen", "dir": "right" },
        { "key": 21, "parent": 2, "text": "Planning", "brush": "darkseagreen", "dir": "right" },
        { "key": 211, "parent": 21, "text": "Priorities", "brush": "darkseagreen", "dir": "right" },
        { "key": 212, "parent": 21, "text": "Ways to focus", "brush": "darkseagreen", "dir": "right" },
        { "key": 22, "parent": 2, "text": "Goals", "brush": "darkseagreen", "dir": "right" },
        { "key": 3, "parent": 0, "text": "Time wasting", "brush": "palevioletred", "dir": "left" },
        { "key": 31, "parent": 3, "text": "Too many meetings", "brush": "palevioletred", "dir": "left" },
        { "key": 32, "parent": 3, "text": "Too much time spent on details", "brush": "palevioletred", "dir": "left" },
        { "key": 33, "parent": 3, "text": "Message fatigue", "brush": "palevioletred", "dir": "left" },
        { "key": 331, "parent": 31, "text": "Check messages less", "brush": "palevioletred", "dir": "left" },
        { "key": 332, "parent": 31, "text": "Message filters", "brush": "palevioletred", "dir": "left" },
        { "key": 4, "parent": 0, "text": "Key issues", "brush": "coral", "dir": "left" },
        { "key": 41, "parent": 4, "text": "Methods", "brush": "coral", "dir": "left" },
        { "key": 42, "parent": 4, "text": "Deadlines", "brush": "coral", "dir": "left" },
        { "key": 43, "parent": 4, "text": "Checkpoints", "brush": "coral", "dir": "left" }
      ]
    };

    function init() {
      // Since 2.2 you can also author concise templates with method chaining instead of GraphObject.make
      // For details, see https://gojs.net/latest/intro/buildingObjects.html
      const $ = go.GraphObject.make;

      myDiagram = new go.Diagram('myDiagramDiv', {
        // when the user drags a node, also move/copy/delete the whole subtree starting with that node
        'commandHandler.copiesTree': true,
        'commandHandler.copiesParentKey': true,
        'commandHandler.deletesTree': true,
        'draggingTool.dragsTree': true,
        'undoManager.isEnabled': true,
      });

      // when the document is modified, add a "*" to the title and enable the "Save" button
      myDiagram.addDiagramListener('Modified', (e) => {
        var button = document.getElementById('SaveButton');
        if (button) button.disabled = !myDiagram.isModified;
        var idx = document.title.indexOf('*');
        if (myDiagram.isModified) {
          if (idx < 0) document.title += '*';
        } else {
          if (idx >= 0) document.title = document.title.slice(0, idx);
        }
      });

      // a node consists of some text with a line shape underneath
      myDiagram.nodeTemplate = $(go.Node,
        'Vertical',
        { selectionObjectName: 'TEXT' },
        $(go.TextBlock,
          {
            name: 'TEXT',
            minSize: new go.Size(30, 15),
            editable: true,
          },
          // remember not only the text string but the scale and the font in the node data
          new go.Binding('text', 'text').makeTwoWay(),
          new go.Binding('scale', 'scale').makeTwoWay(),
          new go.Binding('font', 'font').makeTwoWay()
        ),
        $(go.Shape,
          'LineH',
          {
            stretch: go.Stretch.Horizontal,
            strokeWidth: 3,
            height: 3,
            // this line shape is the port -- what links connect with
            portId: '',
            fromSpot: go.Spot.LeftRightSides,
            toSpot: go.Spot.LeftRightSides,
          },
          new go.Binding('stroke', 'brush'),
          // make sure links come in from the proper direction and go out appropriately
          new go.Binding('fromSpot', 'dir', (d) => spotConverter(d, true)),
          new go.Binding('toSpot', 'dir', (d) => spotConverter(d, false))
        ),
        // remember the locations of each node in the node data
        new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
        // make sure text "grows" in the desired direction
        new go.Binding('locationSpot', 'dir', (d) => spotConverter(d, false))
      );

      // selected nodes show a button for adding children
      myDiagram.nodeTemplate.selectionAdornmentTemplate = $(go.Adornment,
        'Spot',
        $(go.Panel,
          'Auto',
          // this Adornment has a rectangular blue Shape around the selected node
          $(go.Shape, { fill: null, stroke: 'dodgerblue', strokeWidth: 3 }),
          $(go.Placeholder, { margin: new go.Margin(4, 4, 0, 4) })
        ),
        // and this Adornment has a Button to the right of the selected node
        $('Button',
          {
            alignment: go.Spot.Right,
            alignmentFocus: go.Spot.Left,
            click: addNodeAndLink, // define click behavior for this Button in the Adornment
          },
          $(go.TextBlock,
            '+', // the Button content
            { font: 'bold 8pt sans-serif' }
          )
        )
      );

      // the context menu allows users to change the font size and weight,
      // and to perform a limited tree layout starting at that node
      myDiagram.nodeTemplate.contextMenu = $('ContextMenu',
        $('ContextMenuButton', $(go.TextBlock, 'Bigger'), { click: (e, obj) => changeTextSize(obj, 1.1) }),
        $('ContextMenuButton', $(go.TextBlock, 'Smaller'), { click: (e, obj) => changeTextSize(obj, 1 / 1.1) }),
        $('ContextMenuButton', $(go.TextBlock, 'Bold/Normal'), { click: (e, obj) => toggleTextWeight(obj) }),
        $('ContextMenuButton', $(go.TextBlock, 'Copy'), { click: (e, obj) => e.diagram.commandHandler.copySelection() }),
        $('ContextMenuButton', $(go.TextBlock, 'Delete'), { click: (e, obj) => e.diagram.commandHandler.deleteSelection() }),
        $('ContextMenuButton', $(go.TextBlock, 'Undo'), { click: (e, obj) => e.diagram.commandHandler.undo() }),
        $('ContextMenuButton', $(go.TextBlock, 'Redo'), { click: (e, obj) => e.diagram.commandHandler.redo() }),
        $('ContextMenuButton', $(go.TextBlock, 'Layout'), {
          click: (e, obj) => {
            var adorn = obj.part;
            adorn.diagram.startTransaction('Subtree Layout');
            layoutTree(adorn.adornedPart);
            adorn.diagram.commitTransaction('Subtree Layout');
          },
        })
      );

      // a link is just a Bezier-curved line of the same color as the node to which it is connected
      myDiagram.linkTemplate = $(go.Link,
        {
          curve: go.Curve.Bezier,
          fromShortLength: -2,
          toShortLength: -2,
          selectable: false,
        },
        $(go.Shape,
          { strokeWidth: 3 },
          new go.Binding('stroke', 'toNode', (n) => {
            if (n.data.brush) return n.data.brush;
            return 'black';
          }).ofObject()
        )
      );

      // the Diagram's context menu just displays commands for general functionality
      myDiagram.contextMenu = $('ContextMenu',
        $('ContextMenuButton',
          $(go.TextBlock, 'Paste'),
          { click: (e, obj) => e.diagram.commandHandler.pasteSelection(e.diagram.toolManager.contextMenuTool.mouseDownPoint) },
          new go.Binding(
            'visible',
            '',
            (o) => o.diagram && o.diagram.commandHandler.canPasteSelection(o.diagram.toolManager.contextMenuTool.mouseDownPoint)
          ).ofObject()
        ),
        $('ContextMenuButton',
          $(go.TextBlock, 'Undo'),
          { click: (e, obj) => e.diagram.commandHandler.undo() },
          new go.Binding('visible', '', (o) => o.diagram && o.diagram.commandHandler.canUndo()).ofObject()
        ),
        $('ContextMenuButton',
          $(go.TextBlock, 'Redo'),
          { click: (e, obj) => e.diagram.commandHandler.redo() },
          new go.Binding('visible', '', (o) => o.diagram && o.diagram.commandHandler.canRedo()).ofObject()
        ),
        $('ContextMenuButton', $(go.TextBlock, 'Save'), { click: (e, obj) => save() }),
        $('ContextMenuButton', $(go.TextBlock, 'Load'), { click: (e, obj) => load() })
      );

      myDiagram.addDiagramListener('SelectionMoved', (e) => {
        var rootX = myDiagram.findNodeForKey(0).location.x;
        myDiagram.selection.each((node) => {
          if (node.data.parent !== 0) return; // Only consider nodes connected to the root
          var nodeX = node.location.x;
          if (rootX < nodeX && node.data.dir !== 'right') {
            updateNodeDirection(node, 'right');
          } else if (rootX > nodeX && node.data.dir !== 'left') {
            updateNodeDirection(node, 'left');
          }
          layoutTree(node);
        });
      });

      myDiagram.model = go.Model.fromJson(jsonVal);
      if (myDiagram.nodes.any(n => !n.location.isReal())) layoutAll();
    }

    function spotConverter(dir, from) {
      if (dir === 'left') {
        return from ? go.Spot.Left : go.Spot.Right;
      } else {
        return from ? go.Spot.Right : go.Spot.Left;
      }
    }

    function changeTextSize(obj, factor) {
      var adorn = obj.part;
      adorn.diagram.startTransaction('Change Text Size');
      var node = adorn.adornedPart;
      var tb = node.findObject('TEXT');
      tb.scale *= factor;
      adorn.diagram.commitTransaction('Change Text Size');
    }

    function toggleTextWeight(obj) {
      var adorn = obj.part;
      adorn.diagram.startTransaction('Change Text Weight');
      var node = adorn.adornedPart;
      var tb = node.findObject('TEXT');
      // assume "bold" is at the start of the font specifier
      var idx = tb.font.indexOf('bold');
      if (idx < 0) {
        tb.font = 'bold ' + tb.font;
      } else {
        tb.font = tb.font.slice(idx + 5);
      }
      adorn.diagram.commitTransaction('Change Text Weight');
    }

    function updateNodeDirection(node, dir) {
      myDiagram.model.setDataProperty(node.data, 'dir', dir);
      // recursively update the direction of the child nodes
      var chl = node.findTreeChildrenNodes(); // gives us an iterator of the child nodes related to this particular node
      while (chl.next()) {
        updateNodeDirection(chl.value, dir);
      }
    }

    function addNodeAndLink(e, obj) {
      var adorn = obj.part;
      var diagram = adorn.diagram;
      diagram.startTransaction('Add Node');
      var oldnode = adorn.adornedPart;
      var olddata = oldnode.data;
      // copy the brush and direction to the new node data
      var newdata = { text: 'idea', brush: olddata.brush, dir: olddata.dir, parent: olddata.key };
      diagram.model.addNodeData(newdata);
      layoutTree(oldnode);
      diagram.commitTransaction('Add Node');

      // if the new node is off-screen, scroll the diagram to show the new node
      var newnode = diagram.findNodeForData(newdata);
      if (newnode !== null) diagram.scrollToRect(newnode.actualBounds);
    }

    function layoutTree(node) {
      if (node.isTreeRoot) {  // is this a root node?
        // adding to the root?
        layoutAll(); // lay out everything
      } else {
        // otherwise lay out only the subtree starting at this parent node
        var parts = node.findTreeParts();
        layoutAngle(parts, node.data.dir === 'left' ? 180 : 0);
      }
    }

    function layoutAngle(parts, angle) {
      var layout = new go.TreeLayout({
        angle: angle,
        arrangement: go.TreeArrangement.FixedRoots,
        nodeSpacing: 5,
        layerSpacing: 20,
        setsPortSpot: false, // don't set port spots since we're managing them with our spotConverter function
        setsChildPortSpot: false,
      });
      layout.doLayout(parts);
    }

    function layoutAll() {
      var root = myDiagram.findTreeRoots().first();
      if (root === null) return;
      myDiagram.startTransaction('Layout');
      // split the nodes and links into two collections
      var rightward = new go.Set(/*go.Part*/);
      var leftward = new go.Set(/*go.Part*/);
      root.findLinksConnected().each((link) => {
        var child = link.toNode;
        if (child.data.dir === 'left') {
          leftward.add(root); // the root node is in both collections
          leftward.add(link);
          leftward.addAll(child.findTreeParts());
        } else {
          rightward.add(root); // the root node is in both collections
          rightward.add(link);
          rightward.addAll(child.findTreeParts());
        }
      });
      // do one layout and then the other without moving the shared root node
      layoutAngle(rightward, 0);
      layoutAngle(leftward, 180);
      myDiagram.commitTransaction('Layout');
    }

    // Show the diagram's model in JSON format
    function save() {
      document.getElementById('mySavedModel').value = myDiagram.model.toJson();
      myDiagram.isModified = false;
    }
    function load() {
      myDiagram.model = go.Model.fromJson(document.getElementById('mySavedModel').value);
      // if any nodes don't have locations assigned from the model, force a layout of everything
      if (myDiagram.nodes.any(n => !n.location.isReal())) layoutAll();
    }
    window.addEventListener('DOMContentLoaded', init);
  </script>
</body>

</html>